package com.jy.api.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;

@Aspect
@Component
public class RedisLockerAspect {
    @Autowired
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.jy.api.aspect.RedisLocker)")
    public void controllerAspect() {
    }

    @Around("controllerAspect()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        RedisLocker locker = getLockKey(pjp);
        String lockKey = locker.lockKey();
        boolean hasLocked = lock(lockKey, locker.holdTime());
        if (hasLocked) {
            try {
                pjp.proceed();
                //jedisCluster.del(lockKey);//必有过期时间，不作删除操作
            } catch (Exception ex) {
                ex.printStackTrace();
                //jedisCluster.del(lockKey);
                throw ex;
            }
        }

    }

    public RedisLocker getLockKey(JoinPoint joinPoint) throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        RedisLocker rtnValue = null;
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    rtnValue = method.getAnnotation(RedisLocker.class);
                    break;
                }
            }
        }
        return rtnValue;
    }

    private boolean lock(String key, int holdTime) {
        String lock = String.format("LOCK(%s)", key);
        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            long expireAt = System.currentTimeMillis() + holdTime + 1;
            Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
            if (acquire) {
                connection.expire(lock.getBytes(), holdTime);
                return true;
            } else {
                byte[] value = connection.get(lock.getBytes());
                if (Objects.nonNull(value) && value.length > 0) {
                    long expireTime = Long.parseLong(new String(value));
                    if (expireTime < System.currentTimeMillis()) {
                        byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + holdTime + 1).getBytes());
                        return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                    }
                }
            }
            return false;
        });
    }
}
